package com.streambolics.worseminton.data;

import javax.jdo.PersistenceManager;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.Inheritance;
import javax.jdo.annotations.InheritanceStrategy;
import javax.jdo.annotations.NotPersistent;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.internet.MimeMultipart;

import com.streambolics.mail.EmailTextBuilder;
import com.streambolics.mail.SendMail;
import com.streambolics.std.AnchorBuilder;
import com.streambolics.std.HtmlBuilder;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
@Inheritance(strategy = InheritanceStrategy.NEW_TABLE)
public class Invitation extends WorsemintonGuestObject
{
    @Persistent
    Long                     eventId;
    @Persistent
    Boolean                  invited;
    @Persistent
    Boolean                  accepted;
    @Persistent
    Boolean                  declined;
    @Persistent
    String                   comment;
    @Persistent
    String                   privateComment;
    @Persistent
    Long                     generalGenerationCount;
    @Persistent
    Long                     guestGenerationCount;
    @Persistent
    Long                     mailCount;

    @NotPersistent
    private Event            _Event;

    @NotPersistent
    private LocationInterest _Interest;

    public Invitation(long aEventId, LocationInterest aLocationInterest)
    {
        super (aLocationInterest.getGuestId ());
        eventId = aEventId;
    }

    public static Invitation create (PersistenceManager aDatabase, long aEventId, LocationInterest aLocationInterest)
    {
        Invitation i = new Invitation (aEventId, aLocationInterest);
        i.setDatabase (aDatabase);
        aDatabase.makePersistent (i);
        return i;
    }

    public static Invitation load (PersistenceManager aDatabase, long aId)
    {
        Invitation i = aDatabase.getObjectById (Invitation.class, aId);
        i.setDatabase (aDatabase);
        return i;
    }

    /**
     * Bump the guest generation count of the corresponding event.
     */

    private void bump ()
    {
        getEvent ().bumpGuestGenerationCount ();
    }

    /**
     * Declares that this invitation is up to date and that no mail needs to be
     * sent.
     */

    public void declareUpToDate ()
    {
        generalGenerationCount = getEvent ().getGeneralGenerationCount ();
        guestGenerationCount = getEvent ().getGuestGenerationCount ();
    }

    public void generateEmail (EmailTextBuilder aBody, boolean aIncludeMailLink)
    {
        aBody.addParagraph ("Dear badminton player,");
        if (aIncludeMailLink)
        {
            aBody.addAnchor ("http://worseminton.appspot.com/go/InvitationMail?ID=" + getIdAsString (),
                    "If you can't read this e-mail, or to see a web-based version, follow this link");
        }
        if (isOwner ())
        {
            aBody.addParagraph ("You receive this mail from the Even Worse Minton Administrator (EWMA) " + " because you initiated a badminton event:");
        }
        else
        {
            aBody.addParagraph ("You receive this mail from the Even Worse Minton Administrator (EWMA) on behalf of " + getEvent ().getOwner ().getBestName ()
                    + " because you were invited to a badminton event:");
        }

        getEvent ().generateMailDescription (aBody);

        aBody.addAnchor ("http://worseminton.appspot.com/go/InvitationAccept?ID=" + getIdAsString (), "Click here to accept the invitation.");
        aBody.addParagraph ("");
        aBody.addAnchor ("http://worseminton.appspot.com/go/Invitation?ID=" + getIdAsString (),
                "Click here to change other options (including opting out from receiving mail from Even Worse Badminton Administrator)");
        aBody.addParagraph ("");

        // TODO Display a large description of the event here.

    }

    public void generateGuestHtml (HtmlBuilder aBuilder)
    {
        generateNameText (aBuilder);
        aBuilder.addText (" ");
    }

    public void generateGuestListHtml (HtmlBuilder aBuilder)
    {
        getEvent ().generateGuestListHtml (aBuilder);
        aBuilder.addText ("This is the list of guests");
    }

    public void generateReplyAnchors (AnchorBuilder aBuilder)
    {
        generateIdLink (aBuilder, "/go/InvitationAccept", "Accept");
        generateIdLink (aBuilder, "/go/InvitationDecline", "Decline");
        generateIdLink (aBuilder, "/go/InvitationComment", "Comment");
    }

    public void generateShowDetailsAnchor (AnchorBuilder aBuilder)
    {
        generateIdLink (aBuilder, "/go/Invitation", getEvent ().getDescription ());
    }

    public Event getEvent ()
    {
        if (_Event == null)
        {
            _Event = Event.load (getDatabase (), eventId);
        }
        return _Event;
    }

    public long getEventId ()
    {
        return eventId;
    }

    public long getGeneralGenerationCount ()
    {
        return generalGenerationCount == null ? 0 : generalGenerationCount;
    }

    public long getGuestGenerationCount ()
    {
        return guestGenerationCount == null ? 0 : guestGenerationCount;
    }

    public String getICalendarText ()
    {
        // TODO : Replace this with iCal4j

        StringBuilder sb = new StringBuilder ();
        sb.append ("BEGIN:VCALENDAR\n");
        sb.append ("VERSION:2.0\n");
        sb.append ("METHOD:REQUEST\n");
        sb.append ("BEGIN:VEVENT\n");
        sb.append ("ORGANIZER:MAILTO:invitation@worseminton.appspotmail.com\n");

        // TODO : Format the real date here...

        sb.append ("DTSTART:201201011000\n");
        sb.append ("DTEND:201201011100\n");
        sb.append ("LOCATION:");
        sb.append (getEvent ().getLocation ().getName ());
        sb.append ("\n");
        sb.append ("DESCRIPTION:");
        sb.append (getEvent ().getDescription ());
        sb.append ("\n\n");
        sb.append ("END:VEVENT\n");
        sb.append ("END:VCALENDAR\n");
        return sb.toString ();
    }

    public LocationInterest getInterest ()
    {
        if (_Interest == null)
        {
            _Interest = LocationInterest.access (getDatabase (), getEvent ().getLocationId (), getGuestId ());
        }
        return _Interest;
    }

    public Location getLocation ()
    {
        return getEvent ().getLocation ();
    }

    public long getMailCount ()
    {
        return mailCount == null ? 0 : mailCount;
    }

    @Override
    public String getNameHtmlStyle ()
    {
        if (isDeclined ())
        {
            return "invitationdeclined";
        }
        else if (isAccepted ())
        {
            return "invitationaccepted";
        }
        else if (isInvited ())
        {
            return "invitationinvited";
        }
        else
        {
            return "invitationnotinvited";
        }
    }

    public String getPrivateComment ()
    {
        return privateComment;
    }

    public String getStatusText ()
    {
        if (isAccepted ())
        {
            return "will attend";
        }
        if (isDeclined ())
        {
            // TODO : add the comment and the private comment
            return "will *NOT* attend";
        }
        return "pending";
    }

    public boolean isAccepted ()
    {
        return accepted == null ? false : accepted;
    }

    public boolean isDeclined ()
    {
        return declined == null ? false : declined;
    }

    public boolean isGeneralStatusUpToDate ()
    {
        return getEvent ().getGeneralGenerationCount () == getGeneralGenerationCount ();
    }

    public boolean isGuestStatusUpToDate ()
    {
        return getEvent ().getGuestGenerationCount () == getGuestGenerationCount ();
    }

    /**
     * Whether this guest is really invited.
     * 
     * @return
     */

    public boolean isInvited ()
    {
        return invited == null ? true : invited;
    }

    /**
     * Whether this is the invitation made to the owner of the event.
     * 
     * @return True if the guest is the owner of the event, false otherwise.
     */

    public boolean isOwner ()
    {
        return getGuestId () == getEvent ().getOwnerId ();
    }

    /**
     * Whether the event is still pending.
     * 
     * @return The pending state of the event.
     */

    public boolean isPending ()
    {
        return getEvent ().isPending ();
    }

    /**
     * Whether an e-mail has ever been sent to the guest for this event.
     * 
     * @return True if a mail has already been sent, false otherwise.
     */

    public boolean mailEverSent ()
    {
        return getMailCount () != 0;
    }

    /**
     * Sends an e-mail to the guest to inform of changes in the event.
     * 
     * The mail is sent only if the change is worth sending an e-mail, depending
     * on user preferences retrieved from the Location Interest.
     * 
     * @throws Exception
     */

    public void sendEmail () throws Exception
    {
        if (shouldSendEmail ())
        {
            Message m = SendMail.createMessage ("invitation@worseminton.appspotmail.com", isOwner () ? "Worseminton administrator" : getEvent ().getOwner ().getBestName (),
                    getGuest ().getEmail (), getGuest ().getBestName ());

            if (isOwner () || mailEverSent ())
            {
                m.setSubject ("About the badminton event " + getEvent ().getDescription ());
            }
            else
            {
                m.setSubject ("I invite you to play badminton - " + getEvent ().getDescription ());
            }

            EmailTextBuilder tb = new EmailTextBuilder ();

            generateEmail (tb, true);

            Multipart mp = new MimeMultipart ();
            SendMail.addBody (mp, tb);
            // SendMail.addCalendar (mp, getICalendarText ());
            m.setContent (mp);

            // TODO : Attach here the calendar file.

            SendMail.sendMessage (m);
            if (mailCount == null)
            {
                mailCount = 1L;
            }
            else
            {
                mailCount++;
            }
        }
        declareUpToDate ();
    }

    /**
     * Changes the accepted state of the invitation.
     * 
     * @param aAccepted
     *            Whether the guest has accepted the invitation. Setting to true
     *            will automatically reset the declined state, but setting to
     *            false will not set declined to true.
     */

    public void setAccepted (boolean aAccepted)
    {
        if (isAccepted () != aAccepted)
        {
            accepted = aAccepted;
            bump ();
            if (aAccepted)
            {
                setDeclined (false);
            }
        }
    }

    public void setComment (String aComment)
    {
        if (comment == null || !comment.equals (aComment))
        {
            comment = aComment;
            bump ();
        }
    }

    public void setDeclined (boolean aDeclined)
    {
        if (isDeclined () != aDeclined)
        {
            declined = aDeclined;
            bump ();
            if (aDeclined)
            {
                setAccepted (false);
            }
        }
    }

    /**
     * Sets the invited status.
     * 
     * @param aInvited
     *            Whether the guest is invited or not. Setting this to true will
     *            trigger all automatic responses.
     */

    public void setInvited (boolean aInvited)
    {
        if (isInvited () != aInvited)
        {
            invited = aInvited;
            bump ();
            if (invited && !accepted && !declined)
            {
                if (getInterest ().isForwarded ())
                {
                    setDeclined (true);
                    setComment ("Auto response - I no longer use this e-mail address for badminton events...");
                    setPrivateComment ("Please use my other e-mail address: " + getInterest ().getForward ().getEmail ());
                }
                else if (!getInterest ().isInterested ())
                {
                    setDeclined (true);
                    setComment ("Auto response - I am not interested by events in that location...");
                }
            }
        }
    }

    public void setPrivateComment (String aPrivateComment)
    {
        privateComment = aPrivateComment;
    }

    /**
     * Whether an email should be sent.
     * 
     * @return True if the invitation is not up to date, and an email should be
     *         sent to the guest, informing him of the changes. False if the
     *         invitation is up-to-date, or the guest chose not to receive
     *         updates.
     */

    public boolean shouldSendEmail ()
    {
        return getInterest ().isInterested ()
                && ((getInterest ().shouldSendGuestChanges () && !isGuestStatusUpToDate ()) || (getInterest ().shouldSendGeneralChanges () && !isGeneralStatusUpToDate ()));
    }
}
